﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.IO;
using System.Xml;

namespace ParseTraceResulttoCSV
{
    /// <summary>
    /// Class to read Xml Trace file. 
    /// </summary>
    public class DebugTrace
    {
        /// <summary>
        /// List to store all event occured in trace file
        /// </summary>
        private List<ResultObject> xmlTestResults;

        /// <summary>
        /// Hashtable to store execution ids and test ids
        /// </summary>
        private Hashtable xmlTestExecutions = new Hashtable();

        /// <summary>
        /// Constructor
        /// </summary>
        public DebugTrace()
        {
            xmlTestExecutions = new Hashtable();
            xmlTestResults = new List<ResultObject>();
        }

        /// <summary>
        /// Calling method for parsing Trace file
        /// </summary>
        /// <param name="args">filePath</param>
        /// <param name="fileType">generated file type</param>
        public void Run(string args, string fileType = "csv")
        {
            string str = "";
            Hashtable tests = new Hashtable();
            string ext = string.Empty;
            if (!string.IsNullOrEmpty(args))
                ext = (new FileInfo(args)).Extension;

            if (args != null && ext.Contains("trx"))
            {
                try
                {
                    TextReader tr = new StreamReader(args);
                    str = tr.ReadToEnd();
                    XmlDocument resultFile = new XmlDocument();
                    resultFile.LoadXml(str);
                    XmlNamespaceManager atomNamespaceManager = new XmlNamespaceManager(resultFile.NameTable);
                    atomNamespaceManager.AddNamespace("x", @"http://microsoft.com/schemas/VisualStudio/TeamTest/2010");

                    XmlNode appNode = resultFile.SelectSingleNode(@"x:TestRun", atomNamespaceManager);
                    foreach (XmlNode eachTest in appNode.SelectNodes(@"x:TestDefinitions/x:UnitTest", atomNamespaceManager))
                    {
                        string testName = "";
                        string testId = "";
                        foreach (XmlAttribute xmlAttribute in eachTest.Attributes)
                        {
                            if (xmlAttribute.Name == "id")
                                testId = xmlAttribute.Value;
                            if (xmlAttribute.Name == "name")
                                testName = xmlAttribute.Value;
                        }
                        tests.Add(testId, testName);
                    }
                    foreach (XmlNode eachTest in appNode.SelectNodes(@"x:TestDefinitions/x:OrderedTest", atomNamespaceManager))
                    {
                        string testName = "";
                        string testId = "";
                        foreach (XmlAttribute xmlAttribute in eachTest.Attributes)
                        {
                            if (xmlAttribute.Name == "id")
                                testId = xmlAttribute.Value;
                            if (xmlAttribute.Name == "name")
                                testName = xmlAttribute.Value;
                        }
                        tests.Add(testId, testName);
                    }

                    xmlTestExecutions.Clear();
                    testExecutionList(appNode.SelectSingleNode(@"x:TestEntries", atomNamespaceManager), atomNamespaceManager);

                    xmlTestResults.Clear();
                    XmlNode newappnode = appNode.SelectSingleNode(@"x:Results/x:TestResultAggregation/x:InnerResults", atomNamespaceManager);
                    if (newappnode != null)
                        testResultList(newappnode, atomNamespaceManager);

                    foreach (XmlNode testResults in appNode.SelectNodes(@"x:Results/x:UnitTestResult", atomNamespaceManager))
                    {
                        testResult(testResults, atomNamespaceManager);
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception Occured While Reading File.\r\nMessage : " + e.Message);
                }
            }
            else
            {
                Console.WriteLine("Incorrect File. Please Check.");
            }

            if (xmlTestResults.Count > 0)
            {
                try
                {
                    string file = args.Replace(".trx", ".csv");
                    TextWriter tr = new StreamWriter(file);
                    tr.WriteLine("ParentTest,Test,Step,Action,ControlType,ControlProperties,Status,Comment,Duration");
                    foreach (ResultObject resultObject in xmlTestResults)
                    {
                        string parentTest = "";
                        string test = "";
                        if (resultObject.ParentTest != null)
                            parentTest = tests[xmlTestExecutions[resultObject.ParentTest]].ToString();
                        if (resultObject.Test != null)
                            test = tests[resultObject.Test].ToString();
                        resultObject.Action = resultObject.Action == null ? "" : resultObject.Action;
                        resultObject.ControlType = resultObject.ControlType == null ? "" : resultObject.ControlType;
                        resultObject.ControlProperties = resultObject.ControlProperties == null ? "" : resultObject.ControlProperties;
                        resultObject.Comment = resultObject.Comment == null ? "" : resultObject.Comment;
                        tr.WriteLine(parentTest + "," + test + "," + resultObject.Step + "," + resultObject.Action.Replace(",", "_") + "," + resultObject.ControlType.Replace(",", "_") + "," + resultObject.ControlProperties.Replace(",", "_") + "," + resultObject.Status + "," + resultObject.Comment.Replace(",", "_") + "," + resultObject.Duration);

                    }
                    tr.Close();
                    Console.WriteLine("File Created Successfully.");
                    Console.WriteLine(file);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception Occured While Writing File.\r\nMessage : " + e.Message);
                }
            }
        }

        /// <summary>
        /// gets lists of executions made
        /// </summary>
        /// <param name="xml">xml node</param>
        /// <param name="xmlNamespaceManager">namespaces of xml</param>
        private void testExecutionList(XmlNode xml, XmlNamespaceManager xmlNamespaceManager)
        {
            foreach (XmlNode node in xml.SelectNodes(@"x:TestEntry", xmlNamespaceManager))
            {
                string testId = "";
                string executionId = "";
                foreach (XmlAttribute xmlAttribute in node.Attributes)
                {
                    if (xmlAttribute.Name == "testId")
                        testId = xmlAttribute.Value;
                    if (xmlAttribute.Name == "executionId")
                        executionId = xmlAttribute.Value;
                }
                xmlTestExecutions.Add(executionId, testId);
                foreach (XmlNode childNode in node.SelectNodes(@"x:TestEntries", xmlNamespaceManager))
                {
                    testExecutionList(childNode, xmlNamespaceManager);
                }
            }
        }

        /// <summary>
        /// Gets all the detailed result nodes
        /// </summary>
        /// <param name="xml">Result Xml node</param>
        /// <param name="xmlNamespaceManager">namespaces of xml</param>
        private void testResultList(XmlNode xml, XmlNamespaceManager xmlNamespaceManager)
        {
            foreach (XmlNode node in xml.SelectNodes(@"x:UnitTestResult", xmlNamespaceManager))
            {
                testResult(node, xmlNamespaceManager);
            }
            foreach (XmlNode childNode in xml.SelectNodes(@"x:TestResultAggregation/x:InnerResults", xmlNamespaceManager))
            {
                testResultList(childNode, xmlNamespaceManager);
            }
        }

        /// <summary>
        /// Method to read unit test result trace
        /// </summary>
        /// <param name="xml">debug xml node of trcae file</param>
        /// <param name="xmlNamespaceManager">namespaces of xml</param>
        private void testResult(XmlNode xml, XmlNamespaceManager xmlNamespaceManager)
        {
            ResultObject resultObject = new ResultObject();
            foreach (XmlAttribute xmlAttribute in xml.Attributes)
            {
                if (xmlAttribute.Name == "testId")
                    resultObject.Test = xmlAttribute.Value;
                if (xmlAttribute.Name == "parentExecutionId")
                    resultObject.ParentTest = xmlAttribute.Value;
                if (xmlAttribute.Name == "outcome")
                    resultObject.Status = xmlAttribute.Value;
                if (xmlAttribute.Name == "duration")
                    resultObject.Duration = xmlAttribute.Value;
            }
            xmlTestResults.Add(resultObject);
            int i = 0;

            XmlNode testNode = xml.SelectSingleNode(@"x:Output/x:DebugTrace", xmlNamespaceManager);
            string testFile = testNode.InnerText;
            while (testFile.Contains("a class=") || testFile.Contains("Trace Message Log ("))
            {
                int index_a = testFile.IndexOf("a class=");
                int index_Trace = testFile.IndexOf("Trace Message Log (");

                index_Trace = index_Trace == -1 ? index_a + 1 : index_Trace;

                if (testFile.Contains("a class=") && testFile.Contains("Playback - ") && index_a < index_Trace)
                {
                    i++;
                    string row = testFile.Substring(testFile.IndexOf("Playback - "));
                    ResultObject resultObjectTemp = new ResultObject();
                    resultObjectTemp.Test = resultObject.Test;
                    resultObjectTemp.ParentTest = resultObject.ParentTest;
                    resultObjectTemp.Step = i.ToString();
                    xmlTestResults.Add(control(resultObjectTemp, row.Substring(0, row.IndexOf("/a"))));

                    if (row.Contains("a class="))
                        row = row.Substring(row.IndexOf("/a"));

                    testFile = row;
                }
                else if (testFile.Contains("Trace Message Log ("))
                {
                    string message = testFile.Substring(testFile.IndexOf("Trace Message Log (")+19);
                    
                    ResultObject resultObj = new ResultObject();
                    resultObj.ParentTest = resultObject.ParentTest;
                    resultObj.Test = resultObject.Test;
                    resultObj.Step = "=>";
                    resultObj.Action = message.Substring(0, message.IndexOf(")"));

                    xmlTestResults.Add(resultObj);

                    testFile = message;
                }
            }

        }

        private ResultObject control(ResultObject resultObject, string str)
        {
            string text = "";
            text = str.Substring(0, str.IndexOf("ControlType='"));
            text = text.Substring(text.LastIndexOf("["));

            resultObject.ControlType = text + valuebetween(str, "ControlType='", "'");

            if (str.Contains("[SUCCESS]"))
            {
                resultObject.Action = valuebetween(str, "[SUCCESS] ", " - \"" + text);
                resultObject.Status = "SUCCESS";
            }
            else if (str.Contains("[FAILED]"))
            {
                string failure = valuebetween(str, "[FAILED] ", text);
                if (failure.Contains("Search"))
                {
                    resultObject.Comment = failure;
                    resultObject.Action = "Search";
                }
                else
                    resultObject.Action = failure.Replace(" - \"", "");
                resultObject.Status = "FAILED";
                //text = "Searching...." + valuebetween(str, "ControlType='", "'") + " '" + IDValue(str) + "'";
            }

            resultObject.ControlProperties = IDValue(str);

            return resultObject;
        }

        /// <summary>
        /// Returns the properties of control extracted from trace
        /// </summary>
        /// <param name="str">trace log string</param>
        /// <returns>properties seperated by pipes</returns>
        private string IDValue(string str)
        {
            string ret = "";
            string prop = "";
            do
            {
                prop = "";
                if (str.Contains(" DisplayText="))
                {
                    prop = str.Substring(str.IndexOf(" DisplayText=") + 13);
                    prop = "DisplayText=" + valuebetween(prop, "'", "'");
                    str = str.Replace(" DisplayText=", "");
                }
                else if (str.Contains(" FriendlyName="))
                {
                    prop = str.Substring(str.IndexOf(" FriendlyName=") + 14);
                    prop = "FriendlyName=" + valuebetween(prop, "'", "'");
                    str = str.Replace(" FriendlyName=", "");
                }
                else if (str.Contains(" InnerText="))
                {
                    prop = str.Substring(str.IndexOf(" InnerText=") + 11);
                    prop = "InnerText=" + valuebetween(prop, "'", "'");
                    str = str.Replace(" InnerText=", "");
                }
                else if (str.Contains(" Id="))
                {
                    prop = str.Substring(str.IndexOf(" Id=") + 4);
                    prop = "Id=" + valuebetween(prop, "'", "'");
                    str = str.Replace(" Id=", "");
                }
                else if (str.Contains(" TagInstance="))
                {
                    prop = str.Substring(str.IndexOf(" TagInstance=") + 13);
                    prop = "TagInstance=" + valuebetween(prop, "'", "'");
                    str = str.Replace(" TagInstance=", "");
                }
                else if (str.Contains(" Text="))
                {
                    prop = str.Substring(str.IndexOf(" Text=") + 6);
                    prop = "Text=" + valuebetween(prop, "'", "'");
                    str = str.Replace(" Text=", "");
                }
                else if (str.Contains(" Value="))
                {
                    prop = str.Substring(str.IndexOf(" Value=") + 7);
                    prop = "Value=" + valuebetween(prop, "'", "'");
                    str = str.Replace(" Value=", "");
                }
                else if (str.Contains(" ValueAttribute="))
                {
                    prop = str.Substring(str.IndexOf(" ValueAttribute=") + 16);
                    prop = "ValueAttribute=" + valuebetween(prop, "'", "'");
                    str = str.Replace(" ValueAttribute=", "");
                }
                else if (str.Contains(" Name="))
                {
                    prop = str.Substring(str.IndexOf(" Name=") + 6);
                    prop = "Name=" + valuebetween(prop, "'", "'");
                    str = str.Replace(" Name=", "");
                }
                else if (str.Contains(" Instance="))
                {
                    prop = str.Substring(str.IndexOf(" Instance=") + 10);
                    prop = "Instance=" + valuebetween(prop, "'", "'");
                    str = str.Replace(" Instance=", "");
                }

                ret = ret + "|" + prop;
            } while (!string.IsNullOrEmpty(prop));
            return ret;
        }

        /// <summary>
        /// Get string value between to provided strings
        /// </summary>
        /// <param name="atr">main string</param>
        /// <param name="start">start string</param>
        /// <param name="end">end string</param>
        /// <returns>string value between start and end string</returns>
        private string valuebetween(string atr, string start, string end)
        {

            string controltyp = "";
            try
            {
                controltyp = atr.Substring(atr.IndexOf(start) + start.Length);
                controltyp = controltyp.Substring(0, controltyp.IndexOf(end));
            }
            catch { }
            return controltyp;
        }
    }
}
